package com.hzw.saas.service.ds.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hzw.saas.api.ds.IOnlineLevelInfoService;
import com.hzw.saas.api.ds.bo.RankingBo;
import com.hzw.saas.api.ds.bo.RankingInfoBo;
import com.hzw.saas.api.ds.bo.RankingNumBo;
import com.hzw.saas.api.ds.enums.ClientTypeEnum;
import com.hzw.saas.api.ds.query.RankingQuery;
import com.hzw.saas.common.config.annotation.RedisProcessLock;
import com.hzw.saas.common.util.SnowFlakeUtil;
import com.hzw.saas.service.ds.mapper.OnlineLevelInfoMapper;
import com.hzw.saas.service.ds.model.Heartbeat;
import com.hzw.saas.service.ds.model.OnlineLevelInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

@Slf4j
@Service
public class OnlineLevelInfoServiceImpl extends ServiceImpl<OnlineLevelInfoMapper, OnlineLevelInfo> implements IOnlineLevelInfoService, IService<OnlineLevelInfo> {

    // 等级1经验
    public static final Integer LEVEL_ONE = 24 * 60 * 60;
    // 基础倍率
    private static final double BASE_RATIO = 1.3;

    @Resource
    private AsyncService asyncService;

    @Override
    public RankingBo getRankingList(RankingQuery rankingQuery) {
        RankingBo rankingBo = new RankingBo();
        List<RankingInfoBo> rankingList = this.baseMapper.getRankingList(rankingQuery.getClientType(),
                Objects.isNull(rankingQuery.getSize()) ? 100 : rankingQuery.getSize());
        rankingBo.setRankingList(rankingList);

        RankingNumBo userRankingInfo = this.baseMapper.getCurrentUserRankingInfo(rankingQuery.getClientType(), rankingQuery.getUserId());
        rankingBo.setCurrentUserRankingInfo(userRankingInfo);
        // 异步处理等级计算统计
        asyncService.computeBatchLevel(rankingQuery.getClientType());
        return rankingBo;
    }

    @Override
    public int countByType(ClientTypeEnum clientType) {
        return super.lambdaQuery()
                .eq(OnlineLevelInfo::getClientType, clientType)
                .count();
    }

    @Override
    public void initDsUserLevel(String userId) {
        super.lambdaQuery()
                .eq(OnlineLevelInfo::getUserId, userId)
                .eq(OnlineLevelInfo::getClientType, ClientTypeEnum.ds)
                .last(" limit 1")
                .oneOpt()
                .orElseGet(() -> {
                    OnlineLevelInfo onlineLevelInfo = new OnlineLevelInfo()
                            .setLevel(1)
                            .setLevelUpgradeEx(LEVEL_ONE.longValue())
                            .setUserId(userId)
                            .setLevelCurrentEx(0)
                            .setTotalEx(0L)
                            .setClientType(ClientTypeEnum.ds);
                    onlineLevelInfo.setPid(SnowFlakeUtil.nextIdStr());
                    super.save(onlineLevelInfo);
                    return onlineLevelInfo;
                });
    }

    @RedisProcessLock(lockName = "processLevel", key = "#pid")
    public Heartbeat processLevel(Heartbeat heartbeat) {
        if (heartbeat.getCheckStatus() == 1) {
            return heartbeat;
        }
        long seconds = Duration.between(heartbeat.getStartTime(), heartbeat.getEndTime()).getSeconds();
        super.lambdaQuery()
                .eq(OnlineLevelInfo::getUserId, heartbeat.getUserId())
                .eq(OnlineLevelInfo::getClientType, heartbeat.getClientType())
                .last(" limit 1")
                .oneOpt()
                .flatMap(onlineLevelInfo -> {
                    long totalEx = onlineLevelInfo.getTotalEx() + seconds;
                    OnlineLevelInfo computeLevel = computeLevel(totalEx);
                    onlineLevelInfo.setTotalEx(computeLevel.getTotalEx())
                            .setLevelCurrentEx(computeLevel.getLevelCurrentEx())
                            .setLevelUpgradeEx(computeLevel.getLevelUpgradeEx())
                            .setLevel(computeLevel.getLevel());
                    super.updateById(onlineLevelInfo);
                    return Optional.of(onlineLevelInfo);
                })
                .orElseGet(() -> {
                    OnlineLevelInfo computeLevel = computeLevel(seconds);
                    OnlineLevelInfo onlineLevelInfo = new OnlineLevelInfo()
                            .setUserId(heartbeat.getUserId())
                            .setClientType(heartbeat.getClientType())
                            .setLevel(computeLevel.getLevel())
                            .setTotalEx(computeLevel.getTotalEx())
                            .setLevelCurrentEx(computeLevel.getLevelCurrentEx())
                            .setLevelUpgradeEx(computeLevel.getLevelUpgradeEx());
                    onlineLevelInfo.setPid(SnowFlakeUtil.nextIdStr());
                    super.save(onlineLevelInfo);
                    return onlineLevelInfo;
                });
        return heartbeat;
    }

    private OnlineLevelInfo computeLevel(Long totalEx) {
        long nextTotal = 0;
        int nextLevel = 0;
        double currentLevelUp = LEVEL_ONE;
        while (nextTotal <= totalEx) {
            if (nextLevel > 0) {
                currentLevelUp = currentLevelUp * BASE_RATIO;
            }
            nextTotal = (int) currentLevelUp + nextTotal;
            nextLevel++;
        }
        OnlineLevelInfo computeResult = new OnlineLevelInfo();
        computeResult.setTotalEx(totalEx)
                .setLevel(nextLevel)
                .setLevelCurrentEx((int) (currentLevelUp - (nextTotal - totalEx)))
                .setLevelUpgradeEx((long) currentLevelUp);
        return computeResult;
    }
}
